package crypter

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
	"time"

	"gitee.com/gousing/helper/internal"
	"gitee.com/gousing/helper/xstring"
)

/**
 * DZ经典加解密函数Go版本(支持UTF-8字符, 支持多字节汉字或特殊符号)
 * 使用Unicode(10进制) 处理待加密原始字符串, 前后端多语言统一支持, 方便数据交换
 * -----------------------------------------------------------
 * @param bool action true=>ENCODEE / false=>DECODE  加密或解密动作
 * @param string str 加密内容(支持UTF-8字符, 支持多字节汉字或特殊符号)
 * @param string key 密钥, 可以为空
 * @param int expiry [可选]有效时间秒（秒）
 * @returns string
 */

func _authCode(isEncode bool, str, key string, expirys ...int64) string {
	str = strings.TrimSpace(str)
	if len(str) < 1 {
		return ""
	}

	// 密匙
	key = _md5String(strings.TrimSpace(key))
	var expiry int64 = 0
	if len(expirys) > 0 && expirys[0] > 0 {
		expiry = expirys[0]
	}
	// 动态密匙长度, 相同的明文会生成不同密文就是依靠动态密匙
	ckey_length := 4
	// 密匙a会参与加解密
	keya := _md5String(key[:16])
	// 密匙b会用来做数据完整性验证
	keyb := _md5String(key[16:])
	// 密匙c用于变化生成的密文
	var keyc string
	if ckey_length != 0 {
		if !isEncode {
			keyc = str[:ckey_length]
		} else {
			keyc = _md5String(time.Now().String())[32-ckey_length:]
		}
	}
	var dataBuf = xstring.NewBuffer(len(str)*2 + 50)
	defer func() {
		dataBuf = nil
	}()
	// 参与运算的密匙
	cryptkey := keya + _md5String(keya+keyc)
	key_length := len(cryptkey)
	// 明文, 前10位用来保存时间戳, 解密时验证数据有效性, 10到26位用来保存$keyb(密匙b), 解密时会通过这个密匙验证数据完整性
	// 如果是解码的话, 会从第 ckey_length 位开始, 因为密文前 ckey_length 位保存 动态密匙, 以保证解密正确
	if !isEncode {
		strBytes, err := _base64Decode(str[ckey_length:])
		if err != nil {
			return ""
		}
		dataBuf.Write(strBytes)
	} else {
		if expiry != 0 {
			expiry = expiry + time.Now().Unix()
		}
		dataBuf.WriteString(fmt.Sprintf("%010d", expiry))
		dataBuf.WriteString(_md5String(str + keyb)[:16])
		dataBuf.WriteString(str)
	}
	var rndkey, box [256]int
	// 产生密匙簿
	for i := 0; i < 256; i++ {
		rndkey[i] = int(cryptkey[i%key_length])
		box[i] = i
	}
	// 用固定的算法, 打乱密匙簿, 增加随机性, 好像很复杂, 实际上并不会增加密文的强度
	for i, j := 0, 0; i < 256; i++ {
		j = (j + box[i] + rndkey[i]) % 256
		tmp := box[i]
		box[i] = box[j]
		box[j] = tmp
	}
	// 核心加解密部分
	var codeBuf = xstring.NewBuffer(len(str)*2 + 50)
	defer func() {
		codeBuf = nil
	}()
	//result := ""
	a, j := 0, 0
	for s := range dataBuf.IterRunes() {
		a = (a + 1) % 256
		j = (j + box[a]) % 256
		tmp := box[a]
		box[a] = box[j]
		box[j] = tmp
		// 从密匙簿得出密匙进行异或, 再转成字符
		//fmt.Println(i, string(s), int(s)^box[(box[a]+box[j])%256])
		//result += string(rune(int(s) ^ box[(box[a]+box[j])%256]))
		codeBuf.WriteRune(rune(int(s) ^ box[(box[a]+box[j])%256]))
	}

	if !isEncode {
		// substr($result, 0, 10) == 0 验证数据有效性
		// substr($result, 0, 10) - time() > 0 验证数据有效性
		// substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16) 验证数据完整性
		// 验证数据有效性, 请看未加密明文的格式
		/*
			frontTen, _ := strconv.ParseInt(result[:10], 10, 0)
			if (frontTen == 0 || frontTen-time.Now().Unix() > 0) && result[10:26] == _md5String(result[26:] + keyb)[:16] {
				return result[26:]
			} else {
				return ""
			}
		*/
		frontTen, _ := strconv.ParseInt(codeBuf.SubString(0, 10), 10, 0)
		//fmt.Println(codeBuf.SubString(0, 10), frontTen)
		if (frontTen == 0 || frontTen-time.Now().Unix() > 0) && codeBuf.SubString(10, 16) == _md5String(codeBuf.SubString(26, -1) + keyb)[:16] {
			return codeBuf.SubString(26, -1)
		} else {
			return ""
		}
	} else {
		// 把动态密匙保存在密文里, 这也是为什么同样的明文, 生产不同密文后能解密的原因
		//result = keyc + _base64Encode(internal.StringToBytes(result))
		return keyc + _base64Encode(codeBuf.Bytes())
	}
}

type dataCode struct{}

var DataCode = dataCode{}

// Encode 加密
func (dataCode) Encode(str string, key string, expirys ...int64) string {
	return _authCode(true, str, key, expirys...)
}

// Decode 解密
func (dataCode) Decode(str string, key string) string {
	return _authCode(false, str, key)
}

// 前后端统一特殊符号(+/=)便于网络传输, 处理Base64, 以方便参数传输
// _base64Decode
func _base64Decode(str string) ([]byte, error) {
	// 前后端统一, 处理Base64, 以方便参数传输
	// PHP $str = str_replace(array('-', '_'), array('+', '/'), $str);
	// JS str = str.replace(/\-/g, "+").replace(/\_/g, "/");
	// Golang
	/*
		str = strings.ReplaceAll(str, "-", "+")
		str = strings.ReplaceAll(str, "_", "/")
		x := len(str) * 3 % 4
		switch {
		case x == 2:
			str += "=="
		case x == 1:
			str += "="
		}
	*/

	var strBuf = xstring.NewBuffer(len(str) + 2)
	defer func() {
		strBuf = nil
	}()
	strBuf.WriteString(str)
	strBuf.ReplaceAll([]byte{'-'}, []byte{'+'})
	strBuf.ReplaceAll([]byte{'_'}, []byte{'/'})
	strBuf.ReplaceAll([]byte{'='}, []byte{})
	switch len(str) * 3 % 4 {
	case 2:
		strBuf.WriteString("==")
	case 1:
		strBuf.WriteString("=")
	}
	// StdEncoding is the standard base64 encoding, as defined in RFC 4648.
	// StdEncoding = NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
	// URLEncoding is the alternate base64 encoding defined in RFC 4648.
	// URLEncoding = NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")
	decodeBuf := make([]byte, base64.StdEncoding.DecodedLen(strBuf.Len()))
	n, err := base64.StdEncoding.Decode(decodeBuf, strBuf.Bytes())
	return decodeBuf[:n], err
}
func _base64Encode(data []byte) string {

	/*
		str := base64.StdEncoding.EncodeToString(data)
		// 前后端统一, 处理Base64, 以方便参数传输
		// PHP $str = str_replace(array('+', '/', '='), array('-', '_', ''), $data)
		// JS s = s.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=/g, "");
		// Golang
		str = strings.ReplaceAll(str, "+", "-")
		str = strings.ReplaceAll(str, "/", "_")
		return strings.ReplaceAll(str, "=", "")
	*/
	encodeBuf := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
	base64.StdEncoding.Encode(encodeBuf, data)
	encodeBuf = bytes.ReplaceAll(encodeBuf, []byte{'+'}, []byte{'-'})
	encodeBuf = bytes.ReplaceAll(encodeBuf, []byte{'/'}, []byte{'_'})
	encodeBuf = bytes.ReplaceAll(encodeBuf, []byte{'='}, []byte{})
	return internal.BytesToString(encodeBuf)
}

// _md5String 生成32位md5字串
func _md5String(s string) string {
	h := md5.Sum(internal.StringToBytes(s))
	return hex.EncodeToString(h[:])
}
